/*
  LinPVR - Linux Personal Video Recorder
  Copyright (C) 2006 Kamil Pawlowski <kamilpe@gmail.com>

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include "video.h"
#include "debug.h"

/* --- private functions --- */

int capture_frame(capture_t *capture);
int vc_stop(capture_t *capture);
int vc_start(capture_t *capture);
int unmap_buffers(videodev_t *device);


/* --- bodies of functions ---*/

int videodev_open(videodev_t *device, char *file)
{
  DEBUG_ENTER("videodev_open");
  device->videobuff_count = 0;

  /* open device */
  device->fd = open(file, O_RDWR);
  if (device->fd == -1) 
    return ERR_OPEN;

  /* query about device capabilities and save */
  if (ioctl(device->fd, VIDIOC_QUERYCAP, &device->cap) == -1)
    return ERR_NO_V4L2;

  return 0;
}

void videodev_close(videodev_t *device)
{
  DEBUG_ENTER("videodev_close");
  close(device->fd);
}

void videodev_attach(videodev_t *device, capture_t *capture)
{
  DEBUG_ENTER("videodev_attach");

  capture->video_proc.device      = device;
  capture->video_proc.init_device = (vidproc_callback)vc_start;
  capture->video_proc.stop_device = (vidproc_callback)vc_stop;
  capture->video_proc.iframe      = (vidproc_callback)capture_frame;
}

int vc_start(capture_t *capture)
{
  videodev_t *device = capture->video_proc.device;
  enum v4l2_buf_type type;
  struct v4l2_format format;
  struct v4l2_requestbuffers buffers;
  int i;

  DEBUG_ENTER("vc_start");

  /* check if devices support capture capabilities */
  if (!device->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE || 
      !device->cap.capabilities & V4L2_CAP_STREAMING)
    return ERR_NO_CAPTURE;

  /* setup format of frames  */
  memset(&format, 0, sizeof(format));
  format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  format.fmt.pix.width = capture->width;
  format.fmt.pix.height = capture->height;
  format.fmt.pix.field = V4L2_FIELD_ANY;
  format.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR32;
  if (ioctl(device->fd, VIDIOC_S_FMT, &format) == -1)
    return ERR_FORMAT;

  /* request buffers for frames */
  memset(&buffers, 0, sizeof(buffers));
  buffers.count = 20;
  buffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  buffers.memory = V4L2_MEMORY_MMAP;
  if (ioctl(device->fd, VIDIOC_REQBUFS, &buffers) == -1)
    return ERR_BUFFERS_REQUEST;
  if (buffers.count < 2)
    return ERR_BUFFERS_FEW;

  /* map register requested buffers to appliaction memory */
  device->videobuff = (videobuff_t*) g_malloc(buffers.count * sizeof(videobuff_t));
  if (device->videobuff == NULL)
    return ERR_MALLOC;

  for (i=0; i<buffers.count; i++) {
    struct v4l2_buffer buff;
    memset(&buff, 0, sizeof(buff));
    buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buff.memory = V4L2_MEMORY_MMAP;
    buff.index = i;
    /* Now query the buffer, but if it has some error,
       then we unmap all of the previous mapped buffers.
       This is required? */
    if (ioctl(device->fd, VIDIOC_QUERYBUF, &buff) == -1) {
      DEBUG_RETVAL(unmap_buffers(device));
      return ERR_BUFFERS_QUERY;
    }
    /* Now requested buffer has be map, like one step before.
       We unmap all previous mapped buffers if error happend */
    device->videobuff[i].length = buff.length;
    device->videobuff[i].ptr = mmap(NULL, buff.length, PROT_READ | PROT_WRITE,
				    MAP_SHARED, device->fd, buff.m.offset);
    if (device->videobuff[i].ptr == MAP_FAILED) {
      DEBUG_RETVAL(unmap_buffers(device));
      return ERR_MMAP;
    }
    /* increase buffer count for unmap_buffers procedure */
    device->videobuff_count ++;
  }

  /* now send stream on command to device */
  type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (ioctl(device->fd, VIDIOC_STREAMON, &type) == -1) {
    DEBUG_RETVAL(unmap_buffers(device));
    return ERR_STREAMON;
  }
  return 0;
}

int vc_stop(capture_t *capture)
{
  enum v4l2_buf_type type;
  struct v4l2_requestbuffers buffers;
  videodev_t *device = capture->video_proc.device;

  DEBUG_ENTER("vc_stop");
  
  type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (ioctl(device->fd, VIDIOC_STREAMOFF, &type) == -1)
    DEBUG_RETVAL(ERR_STREAMOFF);

  if (unmap_buffers(device) < 0) 
    return ERR_MUNMAP;

  return 0;
}

int unmap_buffers(videodev_t *device) 
{
  int ret = 0;
  DEBUG_ENTER("unmap_buffers");
  for (; device->videobuff_count>0; device->videobuff_count --)
    if (munmap(device->videobuff[device->videobuff_count-1].ptr, 
	       device->videobuff[device->videobuff_count-1].length) == -1) 
      ret = ERR_MUNMAP;

  g_free(device->videobuff);
  return ret;
}

int capture_frame(capture_t *capture)
{
  videodev_t *device = capture->video_proc.device;
  struct v4l2_buffer buff;
  struct timeval tv;
  fd_set fdset;
  int ret, i;
  char *src, *dst;
  
  memset(&buff, 0, sizeof(buff));
  buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  buff.memory = V4L2_MEMORY_MMAP;

  /* wait when data ready */
  FD_ZERO(&fdset);
  FD_SET(device->fd, &fdset);
  tv.tv_sec = 2;
  tv.tv_usec = 0;
  ret = select (device->fd + 1, &fdset, NULL, NULL, &tv);
  if (ret == -1 || ret == 0)
    return ERR_NO_DATA;

  /* if we have a frame dequeue from chain and convert bgr32 to rgb24 */
  ioctl(device->fd, VIDIOC_DQBUF, &buff);

  capture->video_proc.frame_ready = 0;
  src = device->videobuff[buff.index].ptr;
  dst = capture->video_proc.rgb_buffer;
  for (i=0; i < capture->width * capture->height; i++) {
      *(dst)   = *(src+2);
      *(dst+1) = *(src+1);
      *(dst+2) = *(src);
      dst+=3;    src+=4;
  }
  capture->video_proc.frame_ready = 1;
  
  ioctl(device->fd, VIDIOC_QBUF, &buff);
  return 0;
}
